package com.quectel.taskserver.mock.simulation;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.WeightRandom;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.RandomUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.quectel.constant.global.SystemConstants;
import com.quectel.constant.sys.SysConfigKeyConstants;
import com.quectel.core.module.car.dto.CarDto;
import com.quectel.core.module.car.service.CarService;
import com.quectel.core.module.device.dto.DeviceDto;
import com.quectel.core.module.device.dto.DeviceTypeDto;
import com.quectel.core.module.device.service.DeviceService;
import com.quectel.core.module.device.service.DeviceTypeService;
import com.quectel.core.module.village.dto.VillageDto;
import com.quectel.core.module.village.service.VillageService;
import com.quectel.sys.module.sys.service.SysConfigService;
import com.quectel.taskserver.mock.MockLimit;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.common.Response;
import com.quectel.util.http.HttpRestUtils;
import com.quectel.util.kit.PageFetchKit;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.shardingsphere.elasticjob.api.ShardingContext;
import org.apache.shardingsphere.elasticjob.simple.job.SimpleJob;
import org.springframework.stereotype.Component;

import java.security.SecureRandom;
import java.util.*;

import static com.quectel.sdk.hikisc.constant.HikIscConstants.EventEnum.PARKING_LOT_ADMISSION2;
import static com.quectel.sdk.hikisc.constant.HikIscConstants.EventEnum.PARKING_LOT_APPEARANCE2;

@Component
@Slf4j
public class SimulationParkingGateEventMock implements SimpleJob {

    private static final String SIMULATION_PARKING_GATE_TYPE_CODE = "SimulationParkingGate";

    // 未注册车辆
    private static List<String> RANDOM_CAR_POOL = new ArrayList<>();
    // 入场车辆map
    private static Map<String, CarEventStruct> PARK_MAP = new HashMap<>();
    // 停车分钟权重器
    private static List<WeightRandom.WeightObj<Integer>> RANDOM_PARK_TIME_LIST = new ArrayList<>();

    private static final HttpRestUtils HTTP_REST_UTILS = HttpRestUtils.getDefaultInstance();

    @DubboReference
    private VillageService villageService;
    @DubboReference
    private DeviceTypeService deviceTypeService;
    @DubboReference
    private DeviceService deviceService;
    @DubboReference
    private CarService carService;
    @DubboReference
    private SysConfigService sysConfigService;

    static {
        RANDOM_CAR_POOL.add("渝A0001");
        RANDOM_PARK_TIME_LIST.add(new WeightRandom.WeightObj<>(1, 100));
    }

    @Override
    public void execute(ShardingContext shardingContext) {
        if (!MockLimit.enableMock() || MockLimit.mockVillageCodes() == null) {
            return;
        }

        // 离场请求
        final Date currentDate = new Date();
        Iterator<Map.Entry<String, CarEventStruct>> iterator = PARK_MAP.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CarEventStruct> next = iterator.next();
            CarEventStruct value = next.getValue();
            long between = DateUtil.between(value.getEventTime(), currentDate, DateUnit.MINUTE, Boolean.FALSE);
            if (between > value.parkMinutes) {
                value.setEventTime(currentDate);
                Response<Object> outResp = req(value, Boolean.FALSE);
                if (outResp.isSuccess()) {
                    iterator.remove();
                    if (!value.isRegist) {
                        RANDOM_CAR_POOL.add(value.getLicense());
                    }
                }
            }
        }

        SecureRandom secureRandom = new SecureRandom();

        DeviceTypeDto deviceTypeDto = deviceTypeService.selectByCode(SIMULATION_PARKING_GATE_TYPE_CODE);
        Map<String, Object> queryDeviceMap = new HashMap<>();
        queryDeviceMap.put(LambdaUtil.getFieldName(DeviceDto::getDeviceTypeId), deviceTypeDto.getId());

        for (String villageCode : MockLimit.mockVillageCodes()) {

            if (secureRandom.nextBoolean()) {
                continue;
            }

            // 获取小区道闸设备
            VillageDto villageDto = villageService.selectByCode(villageCode);
            queryDeviceMap.put(LambdaUtil.getFieldName(DeviceDto::getVillageId), villageDto.getId());
            List<DeviceDto> deviceDtos = PageFetchKit.fetchAll(r -> deviceService.queryList(r), queryDeviceMap);
            if (CollectionUtils.isEmpty(deviceDtos)) {
                continue;
            }

            DeviceDto inDevice = null;
            DeviceDto outDevice = null;
            for (DeviceDto deviceDto : deviceDtos) {
                if (Objects.nonNull(inDevice) && Objects.nonNull(outDevice)) {
                    break;
                }
                try {
                    JsonNode parse = JacksonUtils.parse(deviceDto.getExtraAttr());
                    JsonNode entrancesOrExits = parse.get("entrancesOrExits");
                    if (entrancesOrExits == null) {
                        continue;
                    }
                    int i = entrancesOrExits.asInt();
                    if (i == 1) {
                        outDevice = deviceDto;
                    } else if (i == 2) {
                        inDevice = deviceDto;
                    } else {
                        inDevice = deviceDto;
                        outDevice = deviceDto;
                    }
                } catch (Exception e) {
                    log.warn("ParkingGate Simulation Task, seek device error.");
                }
            }
            if (Objects.isNull(inDevice)) {
                inDevice = deviceDtos.get(0);
            }
            if (Objects.isNull(outDevice)) {
                outDevice = deviceDtos.get(0);
            }

            // 获取可用车牌
            String license = null;
            Boolean isRegist = Boolean.TRUE;
            if (CollectionUtils.isNotEmpty(RANDOM_CAR_POOL) && secureRandom.nextBoolean()) {
                isRegist = Boolean.FALSE;
                license = RANDOM_CAR_POOL.get(0);
            } else {
                Map<String, Object> queryCarMap = new HashMap<>();
                queryCarMap.put(LambdaUtil.getFieldName(CarDto::getVillageId), villageDto.getId());
                List<CarDto> carDtos = carService.queryList(queryCarMap);
                for (CarDto carDto : carDtos) {
                    if (!PARK_MAP.containsKey(carDto.getLicense())) {
                        license = carDto.getLicense();
                        break;
                    }
                }
            }

            if (StringUtils.isBlank(license)) {
                log.warn("ParkingGate Simulation Task, not found car.");
            }

            // 入场请求
            CarEventStruct build = CarEventStruct.builder()
                    .license(license)
                    .inDeviceCode(inDevice.getUniqueId())
                    .outDeviceCode(outDevice.getUniqueId())
                    .eventTime(DateUtil.offsetSecond(currentDate, -secureRandom.nextInt(60)))
                    .parkMinutes(RandomUtil.weightRandom(RANDOM_PARK_TIME_LIST).next())
                    .isRegist(isRegist)
                    .build();

            Response<Object> inResp = req(build, Boolean.TRUE);
            if (inResp.isSuccess()) {
                boolean remove = RANDOM_CAR_POOL.remove(license);
                if (remove) {
                    PARK_MAP.put(license, build);
                }
            }
        }


    }

    /**
     * 道闸出入场模拟请求
     *
     * @param carEventStruct
     * @param isIn
     * @return
     */
    private Response<Object> req(CarEventStruct carEventStruct, Boolean isIn) {
        Map<String, Object> params = new HashMap<>();
        params.put("deviceCode", Boolean.TRUE.equals(isIn) ? carEventStruct.getInDeviceCode() : carEventStruct.getOutDeviceCode());
        params.put("license", carEventStruct.getLicense());
        params.put("inOrOut", Boolean.TRUE.equals(isIn) ? 1 : 2);
        params.put("eventTime", DateUtils.format(carEventStruct.getEventTime(), DateUtils.FormatType.COMMON));
        String host = sysConfigService.getConfigValueByKey(SysConfigKeyConstants.GATEWAY_INNER_BASE_URL);
        return HTTP_REST_UTILS.post(host + SystemConstants.ApiPath.COLLECT_PATH.getPath() + "/simulation/eventRcv/"
                        + (Boolean.TRUE.equals(isIn) ? PARKING_LOT_ADMISSION2.getCallBackPath() : PARKING_LOT_APPEARANCE2.getCallBackPath()),
                params, null, new TypeReference<Response<Object>>() {
                }
        ).getRight();
    }

    @Data
    @Builder
    private static class CarEventStruct {
        private final String license;
        private final String inDeviceCode;
        private final String outDeviceCode;
        private Date eventTime;
        private final Integer parkMinutes;
        private final boolean isRegist;
    }
}
